package cn.zyl.demo.common.tools;

import cn.zyl.demo.common.enums.CypherNameEnum;
import cn.zyl.demo.common.exception.ToolsException;

import java.security.MessageDigest;
import java.util.Random;

/**
 * MD5Tool工具类说明:md5工具类
 *
 * @author wsz
 * @version v1.0
 * @date 2020-07-23
 */
public class CypherMd5Tool {
    private CypherMd5Tool() {
        throw new IllegalAccessError("CypherMd5Tool");
    }
    /** 16进制常量：255 */
    private static final int X_FF = 0xFF;
    /** 16进制常量：256 */
    private static final int X_100 = 0x100;
    /** 随机盐不足长度16，占位符0 */
    private static final char CHAR_0 = '0';
    private static final int NUM_1 = 1;
    private static final int NUM_2 = 2;
    private static final int NUM_3 = 3;
    /** 随机盐长度16 */
    private static final int NUM_16 = 16;
    /** 密文长度32 */
    private static final int NUM_32 = 32;
    /** 密文和盐合并的长度48 */
    private static final int NUM_48 = 48;
    /** 随机数最大值99999999 */
    private static final int NUM_99999999 = 99999999;

    /**
     * byte[]字节数组 转换成 十六进制字符串
     * @param arr 要转换的byte[]字节数组
     * @return  String 返回十六进制字符串
     */
    private static String hex(byte[] arr) {
        StringBuilder sb = new StringBuilder();
        for (byte b : arr) { sb.append(Integer.toHexString((b & X_FF) | X_100).substring(NUM_1, NUM_3)); }
        return sb.toString();
    }
    /**
     * MD5加密,并把结果由字节数组转换成十六进制字符串
     * @param str 要加密的内容
     * @return String 返回加密后的十六进制字符串
     */
    private static String md5Hex(String str) {
        try {
            //返回实现指定摘要算法MessageDigest对
            MessageDigest md = MessageDigest.getInstance(CypherNameEnum.MD_5.getName());
            //摘要计算
            byte[] bytes = md.digest(str.getBytes());
            return hex(bytes);
        } catch (Exception e) {
            throw new ToolsException("MD5加密执行出现错误",e);
        }
    }
    /**
     * 生成含有随机盐的密码
     * @param passStr 要加密的密码
     * @return String 含有随机盐的密码
     */
    public static String getSaltMd5(String passStr) {
        // 生成一个16位的随机数
        Random random = new Random();
        StringBuilder builder = new StringBuilder(NUM_16);
        //返回伪随机均匀分布int之间0（含）值和规定值（不包括），从该随机数生成器的序列绘制
        builder.append(random.nextInt(NUM_99999999)).append(random.nextInt(NUM_99999999));
        int len = builder.length();
        if (len < NUM_16) {
            for (int i = 0,l = NUM_16 - len ; i < l; i++) { builder.append(CHAR_0); }
        }
        // 生成最终的加密盐
        String salt = builder.toString();
        //密码和盐，加密运算
        passStr = md5Hex(passStr + salt);
        //加密后的密文，和盐合并
        char[] char48 = new char[NUM_48];
        for (int i = 0; i < NUM_48; i += NUM_3) {
            char48[i] = passStr.charAt(i / NUM_3 * NUM_2);
            char c = salt.charAt(i / NUM_3);
            char48[i + 1] = c;
            char48[i + 2] = passStr.charAt(i / NUM_3 * NUM_2 + NUM_1);
        }
        return String.valueOf(char48);
    }



    /**
     * 验证加盐后是否和原密文一致
     * @param password 原密文
     * @param md5str 加密之后的密文
     *@return boolean true表示和原密文一致   false表示和原密文不一致
     */
    public static boolean checkPass(String password, String md5str) {
        //装载 密文
        char[] char32 = new char[NUM_32];
        //装载 盐
        char[] char16 = new char[NUM_16];
        for (int i = 0; i < NUM_48; i += NUM_3) {
            char32[i / NUM_3 * NUM_2] = md5str.charAt(i);
            char32[i / NUM_3 * NUM_2 + NUM_1] = md5str.charAt(i + NUM_2);
            char16[i / NUM_3] = md5str.charAt(i + NUM_1);
        }
        String salt = new String(char16);
        System.out.println(salt);
        System.out.println(String.valueOf(char32));
        return md5Hex(password + salt).equals(String.valueOf(char32));
    }
    
    public static void main(String[] args) {
        String plaintext = "123456789";
        String md5 = CypherMd5Tool.getSaltMd5(plaintext);
        System.out.println("加盐后 MD5：" + md5);
        System.out.println("是否相同:" + CypherMd5Tool.checkPass(plaintext, md5));
    }
}